iT邦幫忙

2022 iThome 鐵人賽

DAY 25
0
Web 3

Road Map To DApp Developer系列 第 25

【DAY25】- Verification System (abi.encodePacked 解析)

  • 分享至 

  • xImage
  •  

Preface

昨日完成了產生 NFT QRcode 的部分,今天起三天我想要來設計出一個驗證系統,主要是透過前端處理後傳入區塊鏈,可以透過鏈上的合約來驗證,確認後將原票卷銷毀(burn),再 mint 一張紀念票卡給使用者(這部分會在後面討論這樣做的原因)。

Verify Flow

前端負責的流程如下:

  1. 將資料做預處理
  2. 轉換成 QRCode
  3. 前端的驗證系統處理
    • 如何取得資料
    • 與合約互動

大致瞭解了驗證的流程後,便可以開始前端的冒險了 (?)。

QRCode Add Salt

在將我們拿到的資料轉換成 QRCode 之前,我們會先將這筆資料做預處理。昨天雖然在程式碼中有寫到但是卻沒有提到,這是因為尚未想好整體的驗證流程。

首先我想要拿來處理得資料就是使用者的 address,不要懷疑就是 address!

原因是這樣的,因為在前面 fetch 資料時已經確認過使用者的 address 是在特定 tokenId 裡面的 owner 了,因此可以確認說使用者是確實擁有這個 ticket。此外我們還會利用加點東西再 hash(),因此就安全性來說應該是可行的(如果不可行可以來證明XD)。

這邊我想加入 "token3_audience_tickets",與 address 一起進行 keccak256(),但很快的我就發現了一些問題:

我原本這樣寫:

const addedMessage = "token3_audience_tickets";
const addedAddress = accounts[0];
const QrData = keccak256(addedMessage, addedAddress);

log 在開發者工具中可以看到其實他會轉換成一個 Uint8 Array

Uint8Array(32) [
196, 238, 197, 42, 97, 187, 55, 41, 83, 198,
11, 84, 211, 172, 190, 28, 147, 138, 196, 66, 
84, 241, 118, 201, 232, 55, 80, 151, 47, 77, 
102, 231, 
buffer: ArrayBuffer(32), 
byteLength: 32, 
byteOffset: 0, 
length: 32, 
Symbol(Symbol.toStringTag): 'Uint8Array']

將他使用 buf2hex() 來轉換可以變成:

const buf2hex = (buffer) => { // buffer is an ArrayBuffer
    return ["0x", ...new Uint8Array(buffer)]
        .map(x => x.toString(16).padStart(2, '0'))
        .join('');
}

console.log(buf2hex(QrData));

得到:

0xc4eec52a61bb372953c60b54d3acbe1c938ac44254f176c9e83750972f4d66e7

用十六進制轉換 196 可得到 c4、238 可以得到 ee...,可以驗證他是正確的轉換過去。

Here Comes The Problem

這時候有點小疑惑發生了,我要怎麼保證我 hash 的結果可以跟鏈上的結果一樣?於是我就又做了下面這件事來確認一下。

solidity 上使用 keccak256()

function getKeccak256() public pure returns(bytes32) {
    string memory a = "token3_audience_tickets";
    address b = 0xaB50035F25F96dd3DEf1A7a9cC4E1C81AD6a7651;
    return (keccak256(abi.encodePacked(a,b)));
}

會得到結果:

0xf3f978628bf8dc3da706e75c320c1c8521579aadb64c9acb5d7d085844615d30

這時組長眉頭一皺。

如果鏈下與鏈上的 hash 結果不同不就沒辦法達到驗證的效果了嗎?

因此我仔細思索後發現了一個問題,在 solidity 中會由 abi.encodePacked() 這個函式先處理過兩個變數之後才會進行,之前有提到這個函式,但沒有深入了解。

根據官方 doc上寫的,abi.encodePacked() 可以把丟進去的變數轉換成 bytes32,並按照順序的將他們排列。

直接來舉例就行了:

function getEncoded() public pure returns(bytes memory) {
    uint16 x = 12;
    bytes1 a = 0x42;
    int16 b = -1;
    return (abi.encodePacked(x, a, b));
}

我寫了一個函式來查看 abi.encodePacked() 回傳的值,最後可以得到:

0x000c42ffff  |
^^^^^^        | uint16(12)
      ^^      | bytes1(0x42)
        ^^^^  | int16(-1)

可以看到這些變數都按照順序的變成 bytes32 形式來表示了。

How To Solve?

了解 abi.encodePacked() 後事情就好做了,我只要做到將這些變數都轉成 bytes32 就行了吧?

但是事情不是憨人所想那麼簡單,我找尋了頗多資料後發現,可以使用 Node.js 內建的 Buffer library 來做到相同的事情,這篇 Stack Overflow 有解釋,大致上的做法就是將資料轉換成 hex 形式的 string,我們再透過 string.concat() 的做法將他們 pack 起來。

但懶人如我想要找尋更簡單的方法:一鍵 encode

事實上 web3 本身就有提供一個函式可以幫助我們做到上述的事情:

web3.utils.encodePacked(addedMessage, addedAddress)
const hashedMessage = keccak256(encodePackedMessage);

最後可以得到:

0xf3f978628bf8dc3da706e75c320c1c8521579aadb64c9acb5d7d085844615d30

如此一來就可以在鏈上也同時驗證啦!

Closing

今天雖然對於專案的進度很少,但是對我的基礎知識又更加深了一點,以前搞不懂的 byte、bytes,現在已經更清楚一點了。另外也搞懂了我一直以來不了解的 abi.encodePacked() 的真正用途是什麼,以及他 encode 的方式,其實獲益良多!

明天會將 QRCode 掃描並取得其中的資料,並在這個頁面中同時做驗證!


上一篇
【DAY24】 - From NFT to QRcode
下一篇
【DAY26】Verification System (Verify On Front-end)
系列文
Road Map To DApp Developer30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言